#pragma once

#include <iostream>
#include <functional>
#include <fcntl.h>
#include <unordered_map>
#include <cassert>

#include "log.hpp"
#include "sock.hpp"
#include "err.hpp"
#include "epoller.hpp"
#include "util.hpp"
#include "protocol.hpp"

namespace tcpserver
{
    class Connection;
    class TcpServer;

    static const uint16_t defaultport = 8080;
    static const int num = 64;

    using func_t = std::function<void(Connection *)>;
    // using handler_t = std::function<void(const std::string &package)>;

    class Connection // 当成一个大号的结构体
    {
    public:
        Connection(int sock, TcpServer *tsp)
            : _sock(sock), _tsp(tsp)
        {
        }
        ~Connection()
        {
        }
        void Register(func_t r, func_t s, func_t e)
        {
            _recver = r;
            _sender = s;
            _excepter = e;
        }
        void Close()
        {
            close(_sock);
        }

    public:
        int _sock;
        // 这样类型的缓冲区只能接收字符串类型的数据，如果你想接收picture video等二进制数据，可以使用字节缓冲区char buffer[NUM]，今天我们就简单搞成string类型
        std::string _inbuffer;
        std::string _outbuffer;

        // 每个结构体内部都有回调方法, 这里为什么要这么设计呢？TODO
        func_t _recver;   // 从_sock读
        func_t _sender;   // 向_sock写
        func_t _excepter; // 处理_sock IO的时候，上面的异常事件
        TcpServer *_tsp;  //??TODO 可以被省略，但是蛋哥想给大家加一下
        uint64_t _lasttime;
    };

    class TcpServer
    {
    public:
        TcpServer(func_t func, int port = defaultport)
            : _service(func), _port(port), _num(num), _revs(nullptr)
        {
        }
        // 事件派发器
        void Dispatcher()
        {
            int timeout = -1;
            while (true)
            {
                Loop(timeout);

                // 遍历connection,计算每一个连接已经有多长时间没有动了
            }
        }
        void EnableReadWrite(Connection *conn, bool readable, bool writeable)
        {
            uint32_t event = (readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET; // 每一个文件描述符都以EPOLLET的模式工作
            _epoller.Control(conn->_sock, event, EPOLL_CTL_MOD);
        }
        ~TcpServer()
        {
            _listensock.Close();
            _epoller.Close();
            if (_revs)
                delete[] _revs;
        }
        void initServer()
        {
            // 1.创建socket
            _listensock.Socket();
            _listensock.Bind(_port);
            _listensock.Listen();
            // 2.创建epoll模型
            _epoller.Create();
            // 3.添加listensock到epoll模型中，因为工作模式是ET，所以需要提前设置文件描述符为非阻塞，然后再将fd添加到红黑树中
            // listensock也是一个fd啊，也要看作一个Connection

            // 1.错误传递方法（类型不匹配，下面的function类型是和function<void(Connection *)>不匹配的，因为下面的function类型中有TcpServer类型(this指针)）
            // (1)func_t tmp = std::function<void(TcpServer, Connection *)>(&TcpServer::Accepter);
            // (2)std::function<void(TcpServer, Connection *)> func = &TcpServer::Accepter;
            // (3)Accepter，直接传Accepter也不行，道理相同
            // 2.正确参数传递的做法
            // (1)std::bind(&TcpServer::Accepter, this, std::placeholders::_1)
            // (2)[this](Connection *conn) {Accepter(conn);}
            AddConnection(_listensock.FD(), EPOLLIN | EPOLLET, std::bind(&TcpServer::Accepter, this, std::placeholders::_1), nullptr, nullptr);
            _revs = new struct epoll_event[num];
        }
    private:
        void AddConnection(int sock, uint32_t events, func_t recver, func_t sender, func_t excepter)
        {
            // 1.首先要为该sock创建Connection，并初始化，将其添加到_Connection中
            if (events & EPOLLET)
                Util::SetNonBlock(sock);                   // 只有关心的事件中有EPOLLET的，我们再给人家设置成非阻塞，人家不关心，你就不要设置，用默认的阻塞即可
            Connection *conn = new Connection(sock, this); //???开辟在堆上了，不在栈帧上，不随函数栈帧的销毁而销毁，释放空间有两种方法，程序员调用delete或进程退出
            // 2.给对应的sock设置对应的回调方法
            conn->Register(recver, sender, excepter);

            // 3.其次将sock与关心事件"写透式"注册到epoll模型中，让epoll帮我们关心
            bool ret = _epoller.AddEvent(sock, events);
            assert(ret);
            (void)ret;

            // 4.将kv添加到_connetions对象中
            _connections.insert(std::pair<int, Connection *>(sock, conn));

            logMessage(DEBUG, "add new sock : %d in epoll and unordered_map", sock);
        }
        void Recver(Connection *conn)
        {
            int flag = 0;
            conn->_lasttime = time(nullptr);
            char buffer[1024];
            while (true) // 需要循环读
            {
                ssize_t s = recv(conn->_sock, buffer, sizeof(buffer) - 1, 0); // 这次是非阻塞读取
                if (s > 0)
                {
                    logMessage(DEBUG, "recv: %d", s);
                    buffer[s] = 0;
                    conn->_inbuffer += buffer; // 将读到的数据放入接收缓冲区
                }
                else if (s == 0)
                {
                    if (conn->_excepter)
                    {
                        conn->_excepter(conn); // 回调异常处理函数
                        return;
                    }
                }
                else
                {
                    if (errno == EAGAIN || errno == EWOULDBLOCK)
                    {
                        flag = 1;
                        break;
                    }
                    else if (errno == EINTR)
                    {
                        continue; // 读取的时候被信号给中断了，没关系，继续重新recv读取
                    }
                    else
                    {
                        if (conn->_excepter)
                        {
                            conn->_excepter(conn); // recv是真的调用出错了，回调异常处理函数
                            break;
                        }
                    }
                }
            }
            if (flag)
                _service(conn);

            // logMessage(DEBUG, "%d号fd -> %s", conn->_sock, conn->_inbuffer.c_str());//因为我们没清空接收缓冲区，所以每次打印出来的数据会累积原来的

            // 到这里，已经把本轮就绪的fd事件中的接收缓冲区中的数据全部拿到sock的inbuffer里面了，下面要做的就是解析出一个完整的报文
            // string onePackage;
            // while (ParseOnePackage(conn->_inbuffer, &onePackage))
            // {
            //     _handler(onePackage);
            // }
        }
        void Sender(Connection *conn)
        {
            conn->_lasttime = time(nullptr);

            while (true)
            {
                ssize_t s = send(conn->_sock, conn->_outbuffer.c_str(), conn->_outbuffer.size(), 0);
                if (s > 0)
                {
                    conn->_outbuffer.erase(0, s);
                    if (conn->_outbuffer.empty())
                        break;
                }
                else
                {
                    if (errno == EAGAIN || errno == EWOULDBLOCK)
                    {
                        break; // 发送缓冲区没有多余的空间了
                    }
                    else if (errno == EINTR)
                    {
                        continue;
                    }
                    else
                    {
                        if (conn->_excepter)
                        {
                            conn->_excepter(conn);
                            break;
                        }
                    }
                }
            }
            // 有可能一次发送数据没有发完，则需要对sock开启对写事件的关心，如果发完了，我们要关闭对写事件的关心，可以让epoll为你操几次心，但不要让epoll一直操心
            if (!conn->_outbuffer.empty())
                conn->_tsp->EnableReadWrite(conn, true, true);
            else
                conn->_tsp->EnableReadWrite(conn, true, false);
        }
        void Excepter(Connection *conn)
        {
            _epoller.Control(conn->_sock, 0, EPOLL_CTL_DEL);
            conn->Close();
            _connections.erase(conn->_sock);
            logMessage(DEBUG, "关闭%d 文件描述符的所有的资源", conn->_sock);

            delete conn; // 这里不释放会发生内存泄露
        }
        void Accepter(Connection *conn)
        {
            // listensock事件已经就绪了，但listensock的接收缓冲区中可能不仅仅只有一个tcp连接，另外由于今天是ET模式，
            // 所以Accepter必须在一轮内将就绪的listensock中的所有tcp连接全部accept拿上来，否则可能会遗漏某些tcp连接，导致客户端无法继续和服务器通信
            for (;;)
            {
                std::cout << "Accepter called ..." << std::endl;
                std::string clientip;
                uint16_t clientport;
                int errcode = 0;
                int sock = _listensock.Accept(&clientip, &clientport, &errcode);
                if (sock > 0)
                {
                    AddConnection(sock, EPOLLET | EPOLLIN, // 读事件要常设，写事件EPOLLOUT要按需设置，不要常设
                                  std::bind(&TcpServer::Recver, this, std::placeholders::_1),
                                  std::bind(&TcpServer::Sender, this, std::placeholders::_1),
                                  std::bind(&TcpServer::Excepter, this, std::placeholders::_1));
                    // 将来我们调用Recver/Sender/Excepter时，第一个参数默认传的就是this，因为bind绑定了Recver这个方法调用时的参数
                    logMessage(DEBUG, "get a new link, info: [%s:%d]", clientip.c_str(), clientport);
                }
                else
                {
                    if (errcode == EAGAIN || errcode == EWOULDBLOCK)
                        break;
                    else if (errcode == EINTR) // 系统调用accept可能被中断，例如收到信号SIGINT
                        continue;
                    else
                        break; // 系统调用accept真的出错了
                }
            }
            std::cout << "Accepter quit ..." << std::endl;
        }

        bool IsconnectionsExists(int sock)
        {
            auto iter = _connections.find(sock);
            return iter != _connections.end();
        }
        void Loop(int timeout)
        {
            int n = _epoller.Wait(_revs, _num, timeout); // 获取已经就绪的事件
            for (int i = 0; i < n; i++)
            {
                int fd = _revs[i].data.fd;
                int event = _revs[i].events;

                // TODO---这里还要再完善一下

                // 将所有的异常问题,全部转化为读写问题. 不加这段代码,服务器依旧可以处理异常事件,因为内核会把异常事件添加到fd的事件集合中,但我们不依赖于内核的行为.
                if (event & EPOLLERR)
                    event |= (EPOLLIN | EPOLLOUT);
                if (event & EPOLLHUP)
                    event |= (EPOLLIN | EPOLLOUT);
                // 下面这两段代码可以直接合并为一个分支语句
                //  if ((event & EPOLLIN) && (fd == _listensock.FD()))
                //  {
                //      _connections[fd]->_recver(_connections[fd]);
                //  }
                //  if ((event & EPOLLIN) && (fd != _listensock.FD()))
                //  {
                //      _connections[fd]->_recver(_connections[fd]);
                //  }
                if (IsconnectionsExists(fd) && (event & EPOLLIN)) // 只要是读事件就绪，执行recver即可，不用区分是否为listensock
                {
                    if (_connections[fd]->_recver) // 有可能程序员在添加Connection连接结构体到哈希表时，忘记添加回调方法了，这里我们也需要判断一下
                        _connections[fd]->_recver(_connections[fd]);
                }
                if (IsconnectionsExists(fd) && (event & EPOLLOUT)) // 只要是写事件就绪，执行sender即可
                {
                    if (_connections[fd]->_sender) // 有可能程序员忘记添加回调方法，这种情况我们不进行IO事件处理
                        _connections[fd]->_sender(_connections[fd]);
                }
            }
        }

    private:
        Sock _listensock;
        epoller _epoller;
        uint16_t _port;
        std::unordered_map<int, Connection *> _connections; // tcpServer里面会存在着大量的Connection，管理他们需要先描述再组织，靠的就是哈希表数据结构来组织
        struct epoll_event *_revs;                          // 存放就绪事件的数组
        int _num;                                           // 数组初始化时的空间大小
        func_t _service;                                    // 网络库一般都会把上层的处理工作暴露出去
        // handler_t _handler;                                 // 未来怎么业务逻辑处理传输层交付给应用层的报文数据
    };

}